-- Main.hs
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Bits (shiftL)
import Data.ByteString qualified as DB
import Data.ByteString.Char8 qualified as DBC (putStrLn)
import Data.List (foldl')
import Data.String (fromString)
import Data.Text qualified as T
import Data.Text.Encoding qualified as Encode (decodeUtf8)
import Data.Word (Word8)
import System.Environment (getArgs)
import System.IO
import Text.Printf

-- word 是单词、offset 是偏移量、expLen 是释义长度。
-- 这就是需要存储索引的数据类型。
data WordIdx = WordIndex
  { word :: T.Text,
    offset :: Int,
    expLen :: Int
  }
  deriving (Show)

searchWord :: T.Text -> [WordIdx] -> Maybe WordIdx
searchWord str [] = Nothing
searchWord str xs
  | wrd < str = searchWord str behind
  | wrd > str = searchWord str front
  | otherwise = Just b
  where
    (front, b : behind) = splitAt (length xs `div` 2) xs
    wrd = T.toLower (word b)

-- getIndexList 函数将 idx 文件中的全部字符串解析成一个 WordIdx 的列表作为输出
getIndexList :: DB.ByteString -> [WordIdx]
getIndexList "" = []
getIndexList str = WordIndex w (byteToInt o) (byteToInt e) : getIndexList left
  where
    w = Encode.decodeUtf8 $ DB.takeWhile (/= 0) str
    o = DB.unpack $ DB.take 4 (DB.drop 1 off)
    e = DB.unpack $ DB.take 4 (DB.drop 5 off)
    off = DB.dropWhile (/= 0) str
    left = DB.drop 9 off

-- byteToInt 函数用来将读到的 4 字节字符串转换成整数
byteToInt :: [Word8] -> Int
byteToInt bs = foldl' (\x y -> shiftL (fromIntegral x) 8 + fromIntegral y) 0 bs

main :: IO ()
main = do
  arg <- getArgs
  case arg of
    [] -> print "Usage: Dict <word>"
    (a : _) -> do
      idctIdx <- DB.readFile "./Dict/longman.idx"
      let is = getIndexList idctIdx
      let result = searchWord (fromString a) is
      case result of
        Nothing -> printf "Word \"%s\" not found" a
        Just wrd -> do
          inh <- openFile "./Dict/longman.dict" ReadMode
          hSeek inh AbsoluteSeek (toInteger $ offset wrd)
          hSetEncoding inh utf8
          explanation <- DB.hGet inh (expLen wrd)
          hClose inh
          DBC.putStrLn explanation